package com.androwit.engine;

import java.util.ArrayList;
import java.util.Collection;

import org.apache.commons.beanutils.BeanUtils;

import com.androwit.engine.mapping.Converter;
import com.androwit.engine.mapping.MappingField;
import com.androwit.engine.mapping.MappingFieldsDefiner;

/**
 * Outputs a CSV from {@link Object} objects
 * 
 * @author Zied Hamdi Founder of http://1vu.fr
 * 
 */
public class MappingWriter<J, K, V, I, O> {
	protected MappingPersister<K, V, I, O> persister;

	public MappingWriter(MappingPersister<K, V, I, O> persister) {
		this.persister = persister;
	}

	public Object convert(MappingFieldsDefiner<J, K, V> fieldsDefiner, I entry)
			throws Exception {
		ArrayList<I> oneEntryList = new ArrayList<I>();
		return convert(fieldsDefiner, oneEntryList);
	}

	public Object convert(MappingFieldsDefiner<J, K, V> fieldsDefiner,
			Collection<? extends I> collection) throws Exception {
		try {
			persister.open();
			O item = null;
			for (I inputItem : collection) {
				item = persister.createOutputItem(inputItem, fieldsDefiner);
				item = convert(fieldsDefiner, inputItem, item);
				persister.flush(item);
			}
			return persister.close(item);
		} catch (Exception ex) {
			throw persister.handleException(ex);
		}
	}

	@SuppressWarnings("unchecked")
	public O convert(MappingFieldsDefiner<J, K, V> fieldsDefiner, I object, O out)
			throws Exception {
		MappingField<J, K, V>[] fields = fieldsDefiner.getMappingFields(object);
		for (int i = 0; i < fields.length; i++) {
			MappingField<J, K, V> field = fields[i];
			Converter<J, V> converter = field.getConverter();
			try {
				if (field.handledSpecifically()) {
					// if the type is handled specifically, we give the entire object
					if (converter != null) {
						// to the converter
						out = persister.writeField(field,
								(V) converter.convert((J) object), out);
					} else {
						// to the persister
						throw new IllegalStateException(
								"Field cannot have a null converter if handledSpecifically() returns true: "
										+ fieldsDefiner);
					}
				} else {
					J value = getValue(object, field);
					if (converter != null)
						out = persister
								.writeField(field, (V) converter.convert(value), out);
					else
						// if the converter is null, we assume the value doesn't need
						// conversion
						out = persister.writeField(field, (V) value, out);
				}
			} catch (Throwable ex) {
				throw new IllegalStateException("unable to convert field " + field
						+ " with converter: " + converter, ex);
			}
		}
		return out;
	}

	@SuppressWarnings("unchecked")
	protected J getValue(Object Object, MappingField<J, K, V> field) {
		try {
			return (J) BeanUtils.getProperty(Object, field.getName());
		} catch (Exception ex) {
			throw new IllegalStateException("Can't access " + Object.getClass()
					+ "'s field " + field.getName(), ex);
		}
	}
}
